1use agentmux_common::{FlexDirection, LayoutNode, ResizeOp, SplitPosition};
17use uuid::Uuid;
18
19#[derive(Debug, Clone, PartialEq)]
22pub enum LayoutError {
23 NodeNotFound { id: String },
24 SelfSwap,
26 RootCannotBeTarget,
28 InvalidSize { node_id: String, size: f32 },
30 InvalidIndexPath,
32}
33
34impl std::fmt::Display for LayoutError {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 match self {
37 Self::NodeNotFound { id } => write!(f, "node not found: {}", id),
38 Self::SelfSwap => write!(f, "cannot swap a node with itself"),
39 Self::RootCannotBeTarget => write!(f, "root node cannot be the target of this operation"),
40 Self::InvalidSize { node_id, size } => write!(f, "invalid size {:.2} for node {}", size, node_id),
41 Self::InvalidIndexPath => write!(f, "index_arr is empty or points to a non-existent location"),
42 }
43 }
44}
45
46pub const DEFAULT_MAX_CHILDREN: usize = 5;
51
52pub const DEFAULT_NODE_SIZE: f32 = 10.0;
55
56fn reverse_flex_direction(dir: FlexDirection) -> FlexDirection {
61 match dir {
62 FlexDirection::Row => FlexDirection::Column,
63 FlexDirection::Column => FlexDirection::Row,
64 }
65}
66
67fn ensure_group_node(node: &mut LayoutNode) {
82 if node.data.is_some() {
83 let old_id = node.id.clone();
84 let old_flex = node.flex_direction;
85 let intermediate = LayoutNode {
86 id: old_id,
87 flex_direction: reverse_flex_direction(old_flex),
88 size: DEFAULT_NODE_SIZE,
89 data: node.data.take(),
90 extra: std::mem::take(&mut node.extra),
95 ..Default::default()
96 };
97 node.id = Uuid::new_v4().to_string();
98 node.children.push(intermediate);
99 }
100}
101
102pub fn find_node_by_id<'a>(tree: &'a LayoutNode, id: &str) -> Option<&'a LayoutNode> {
106 if tree.id == id {
107 return Some(tree);
108 }
109 for child in &tree.children {
110 if let Some(found) = find_node_by_id(child, id) {
111 return Some(found);
112 }
113 }
114 None
115}
116
117pub fn find_node_by_id_mut<'a>(tree: &'a mut LayoutNode, id: &str) -> Option<&'a mut LayoutNode> {
119 if tree.id == id {
120 return Some(tree);
121 }
122 for child in &mut tree.children {
123 if let Some(found) = find_node_by_id_mut(child, id) {
124 return Some(found);
125 }
126 }
127 None
128}
129
130struct InsertCandidate<'a> {
134 node: &'a LayoutNode,
135 index: usize,
136 depth: usize,
137}
138
139fn find_next_insert_location(
144 tree: &LayoutNode,
145 max_children: usize,
146) -> (&LayoutNode, usize) {
147 let mut candidates = Vec::new();
148 collect_insert_candidates(tree, max_children, 1, &mut candidates);
149 candidates.sort_by(|a, b| {
150 let a_score = (a.depth as f64).powi((a.index + max_children) as i32);
151 let b_score = (b.depth as f64).powi((b.index + max_children) as i32);
152 a_score.partial_cmp(&b_score).unwrap_or(std::cmp::Ordering::Equal)
153 });
154 candidates
155 .into_iter()
156 .next()
157 .map(|c| (c.node, c.index))
158 .unwrap_or((tree, tree.children.len()))
159}
160
161fn collect_insert_candidates<'a>(
162 node: &'a LayoutNode,
163 max_children: usize,
164 depth: usize,
165 out: &mut Vec<InsertCandidate<'a>>,
166) {
167 if node.data.is_some() && node.children.is_empty() {
169 out.push(InsertCandidate { node, index: 1, depth });
170 return;
171 }
172 if node.children.len() < max_children {
173 out.push(InsertCandidate {
174 node,
175 index: node.children.len(),
176 depth,
177 });
178 }
179 for child in node.children.iter().rev() {
181 collect_insert_candidates(child, max_children, depth + 1, out);
182 }
183}
184
185pub fn insert_node(root: &mut LayoutNode, node: LayoutNode) {
192 let loc_id = find_next_insert_location(root, DEFAULT_MAX_CHILDREN).0.id.clone();
193 if let Some(target) = find_node_by_id_mut(root, &loc_id) {
194 ensure_group_node(target);
195 target.children.push(node);
196 }
197}
198
199pub fn insert_node_at_index(
217 root: &mut LayoutNode,
218 node: LayoutNode,
219 index_arr: &[usize],
220) -> Result<(), LayoutError> {
221 if index_arr.is_empty() {
222 return Err(LayoutError::InvalidIndexPath);
223 }
224 let mut current = root as *mut LayoutNode;
225 let mut clamped_idx: usize = 0;
226 let last_seg = index_arr.len() - 1;
227
228 for (seg_pos, &idx) in index_arr.iter().enumerate() {
229 let cur = unsafe { &mut *current };
230 if cur.children.is_empty() {
231 clamped_idx = 0;
234 break;
235 }
236 clamped_idx = idx.min(cur.children.len() - 1);
237 if seg_pos < last_seg {
238 current = &mut cur.children[clamped_idx] as *mut LayoutNode;
239 }
240 }
241
242 let parent = unsafe { &mut *current };
243 ensure_group_node(parent);
244 let insert_at = (clamped_idx + 1).min(parent.children.len());
245 parent.children.insert(insert_at, node);
246 Ok(())
247}
248
249pub fn delete_node(root: &mut LayoutNode, node_id: &str) -> Result<(), LayoutError> {
254 if root.id == node_id {
255 return Ok(());
257 }
258 match delete_recursive(root, node_id) {
259 true => Ok(()),
260 false => Err(LayoutError::NodeNotFound { id: node_id.to_string() }),
261 }
262}
263
264fn delete_recursive(node: &mut LayoutNode, target_id: &str) -> bool {
267 let idx = node.children.iter().position(|c| c.id == target_id);
268 if let Some(i) = idx {
269 node.children.remove(i);
271 if node.children.len() == 1 {
274 let sole = node.children.remove(0);
275 node.id = sole.id;
276 node.size = sole.size;
277 node.data = sole.data;
278 node.flex_direction = sole.flex_direction;
279 node.children = sole.children;
280 node.extra = sole.extra;
281 }
282 return true;
283 }
284 for child in &mut node.children {
286 if delete_recursive(child, target_id) {
287 return true;
288 }
289 }
290 false
291}
292
293pub fn move_node(
297 root: &mut LayoutNode,
298 node_id: &str,
299 new_parent_id: &str,
300 index: usize,
301) -> Result<(), LayoutError> {
302 if root.id == node_id || root.id == new_parent_id {
303 if new_parent_id == root.id && root.id != node_id {
304 } else if root.id == node_id {
306 return Err(LayoutError::RootCannotBeTarget);
307 }
308 }
309
310 let node_to_move = find_node_by_id(root, node_id)
312 .ok_or_else(|| LayoutError::NodeNotFound { id: node_id.to_string() })?
313 .clone();
314 if find_node_by_id(root, new_parent_id).is_none() {
316 return Err(LayoutError::NodeNotFound { id: new_parent_id.to_string() });
317 }
318 if find_node_by_id(&node_to_move, new_parent_id).is_some() {
320 return Err(LayoutError::NodeNotFound {
321 id: format!("{} (is a descendant of the moved node {})", new_parent_id, node_id),
322 });
323 }
324
325 let old_parent_id = find_parent_id(root, node_id);
326 let same_parent = old_parent_id.as_deref() == Some(new_parent_id);
327
328 let cur_idx_in_new_parent = if same_parent {
332 find_node_by_id(root, new_parent_id)
333 .and_then(|p| p.children.iter().position(|c| c.id == node_id))
334 } else {
335 None
336 };
337
338 remove_node_from_parent(root, node_id);
340
341 let mut node_with_size = node_to_move;
342 if !same_parent {
343 node_with_size.size = DEFAULT_NODE_SIZE;
344 }
345
346 let effective_index = match cur_idx_in_new_parent {
354 Some(ci) if index > ci => index - 1,
355 _ => index,
356 };
357
358 let new_parent = find_node_by_id_mut(root, new_parent_id).unwrap();
360 ensure_group_node(new_parent);
361 let insert_at = effective_index.min(new_parent.children.len());
362 new_parent.children.insert(insert_at, node_with_size);
363 Ok(())
364}
365
366fn find_parent_id(root: &LayoutNode, child_id: &str) -> Option<String> {
367 if root.children.iter().any(|c| c.id == child_id) {
368 return Some(root.id.clone());
369 }
370 for child in &root.children {
371 if let Some(pid) = find_parent_id(child, child_id) {
372 return Some(pid);
373 }
374 }
375 None
376}
377
378fn remove_node_from_parent(root: &mut LayoutNode, node_id: &str) {
379 if let Some(idx) = root.children.iter().position(|c| c.id == node_id) {
380 root.children.remove(idx);
381 return;
382 }
383 for child in &mut root.children {
384 remove_node_from_parent(child, node_id);
385 }
386}
387
388pub fn swap_nodes(
391 root: &mut LayoutNode,
392 node1_id: &str,
393 node2_id: &str,
394) -> Result<(), LayoutError> {
395 if node1_id == node2_id {
396 return Err(LayoutError::SelfSwap);
397 }
398 if root.id == node1_id || root.id == node2_id {
399 return Err(LayoutError::RootCannotBeTarget);
400 }
401
402 let n1 = find_node_by_id(root, node1_id)
404 .ok_or_else(|| LayoutError::NodeNotFound { id: node1_id.to_string() })?
405 .clone();
406 let n2 = find_node_by_id(root, node2_id)
407 .ok_or_else(|| LayoutError::NodeNotFound { id: node2_id.to_string() })?
408 .clone();
409 let p1_id = find_parent_id(root, node1_id)
410 .ok_or_else(|| LayoutError::NodeNotFound { id: format!("parent of {}", node1_id) })?;
411 let p2_id = find_parent_id(root, node2_id)
412 .ok_or_else(|| LayoutError::NodeNotFound { id: format!("parent of {}", node2_id) })?;
413
414 if find_node_by_id(&n1, node2_id).is_some() || find_node_by_id(&n2, node1_id).is_some() {
418 return Err(LayoutError::RootCannotBeTarget);
419 }
420
421 let idx1 = {
423 let p = find_node_by_id(root, &p1_id).unwrap();
424 p.children.iter().position(|c| c.id == node1_id).unwrap()
425 };
426 let idx2 = {
427 let p = find_node_by_id(root, &p2_id).unwrap();
428 p.children.iter().position(|c| c.id == node2_id).unwrap()
429 };
430
431 let mut n1_swapped = n2.clone();
433 n1_swapped.size = n1.size; let mut n2_swapped = n1.clone();
435 n2_swapped.size = n2.size; let p1 = find_node_by_id_mut(root, &p1_id).unwrap();
439 p1.children[idx1] = n1_swapped;
440
441 let p2 = find_node_by_id_mut(root, &p2_id).unwrap();
442 p2.children[idx2] = n2_swapped;
443
444 Ok(())
445}
446
447pub fn resize_nodes(root: &mut LayoutNode, ops: &[ResizeOp]) -> Result<(), LayoutError> {
451 for op in ops {
453 if !(0.0..=100.0).contains(&op.size) {
454 return Err(LayoutError::InvalidSize {
455 node_id: op.node_id.clone(),
456 size: op.size,
457 });
458 }
459 }
460 for op in ops {
462 if find_node_by_id(root, &op.node_id).is_none() {
463 return Err(LayoutError::NodeNotFound { id: op.node_id.clone() });
464 }
465 }
466 for op in ops {
468 let node = find_node_by_id_mut(root, &op.node_id).unwrap();
469 node.size = op.size;
470 }
471 Ok(())
472}
473
474pub fn replace_node(
478 root: &mut LayoutNode,
479 target_id: &str,
480 new_node: LayoutNode,
481) -> Result<(), LayoutError> {
482 if root.id == target_id {
483 let preserved_size = root.size;
484 *root = new_node;
485 root.size = preserved_size;
486 return Ok(());
487 }
488 let parent_id = find_parent_id(root, target_id)
489 .ok_or_else(|| LayoutError::NodeNotFound { id: target_id.to_string() })?;
490 let parent = find_node_by_id_mut(root, &parent_id).unwrap();
491 let idx = parent.children.iter().position(|c| c.id == target_id).unwrap();
492 let preserved_size = parent.children[idx].size;
493 parent.children[idx] = new_node;
494 parent.children[idx].size = preserved_size;
495 Ok(())
496}
497
498pub fn split_horizontal(
503 root: &mut LayoutNode,
504 target_id: &str,
505 new_node: LayoutNode,
506 position: SplitPosition,
507) -> Result<(), LayoutError> {
508 split_impl(root, target_id, new_node, position, FlexDirection::Row)
509}
510
511pub fn split_vertical(
516 root: &mut LayoutNode,
517 target_id: &str,
518 new_node: LayoutNode,
519 position: SplitPosition,
520) -> Result<(), LayoutError> {
521 split_impl(root, target_id, new_node, position, FlexDirection::Column)
522}
523
524fn split_impl(
525 root: &mut LayoutNode,
526 target_id: &str,
527 new_node: LayoutNode,
528 position: SplitPosition,
529 direction: FlexDirection,
530) -> Result<(), LayoutError> {
531 let _ = find_node_by_id(root, target_id)
533 .ok_or_else(|| LayoutError::NodeNotFound { id: target_id.to_string() })?;
534
535 let parent_id = find_parent_id(root, target_id);
536
537 if let Some(ref pid) = parent_id {
538 let parent = find_node_by_id_mut(root, pid).unwrap();
539 if parent.flex_direction == direction {
540 let idx = parent.children.iter().position(|c| c.id == target_id).unwrap();
542 let insert_at = match position {
543 SplitPosition::Before => idx,
544 SplitPosition::After => idx + 1,
545 };
546 parent.children.insert(insert_at, new_node);
547 return Ok(());
548 }
549 let idx = parent.children.iter().position(|c| c.id == target_id).unwrap();
551 let target = parent.children.remove(idx);
552 let target_size = target.size;
553 let (children_ordered, new_flex) = match position {
554 SplitPosition::Before => (vec![new_node, target], direction),
555 SplitPosition::After => (vec![target, new_node], direction),
556 };
557 let group = LayoutNode {
558 id: Uuid::new_v4().to_string(),
559 flex_direction: new_flex,
560 size: target_size,
561 children: children_ordered,
562 ..Default::default()
563 };
564 parent.children.insert(idx, group);
565 return Ok(());
566 }
567
568 let root_clone = LayoutNode {
573 id: root.id.clone(),
574 flex_direction: root.flex_direction,
575 size: root.size,
576 children: std::mem::take(&mut root.children),
577 data: root.data.take(),
578 extra: std::mem::take(&mut root.extra),
579 };
580 let root_size = root_clone.size;
581 let new_group_id = Uuid::new_v4().to_string();
582 let (children_ordered, new_flex) = match position {
583 SplitPosition::Before => (vec![new_node, root_clone], direction),
584 SplitPosition::After => (vec![root_clone, new_node], direction),
585 };
586 root.id = new_group_id;
587 root.flex_direction = new_flex;
588 root.size = root_size;
589 root.children = children_ordered;
590 root.data = None;
591 Ok(())
592}
593
594pub fn clear_tree_node(_root: &mut Option<LayoutNode>) {
600 *_root = None;
601}
602
603#[cfg(test)]
605mod tests;